package net.cathackers.devel.scmp.xmpp;

import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.QName;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;

import java.io.StringWriter;
import java.util.Iterator;

/**
 * represent error element of any stanza
 * 
 */
public class StanzaErrors {
    /**
     * error namespace
     */
    private static final String ERROR_NAMESPACE = "urn:ietf:params:xml:ns:xmpp-stanzas";
    private static DocumentFactory docFactory = DocumentFactory.getInstance();
    /**
     * error element
     */
    private Element element_;

    /**
     * Constructs a new StanzaError with the specified condition.
     * 
     * @param condition
     *            the error condition
     * @see Condition
     */
    public StanzaErrors(Condition condition) {
	element_ = docFactory.createElement("error");
	setType(condition.getType());
	setCondition(condition);
    }

    /**
     * Constructs a new StanzaError with the specified condition and
     * description.
     * 
     * @param condition
     *            error condition
     * @see Condition
     * @param text
     *            the description of error
     */
    public StanzaErrors(Condition condition, String text) {
	element_ = docFactory.createElement("error");
	setType(condition.getType());
	setCondition(condition);
	setText(text, null);
    }

    /**
     * Constructs a new StanzaError with the specified condition and description
     * and language code.
     * 
     * @param condition
     *            error condition
     * @see Codition
     * @param text
     *            description error
     * @param lang
     *            the language code of the error description (e.g. "en")
     */
    public StanzaErrors(Condition condition, String text, String lang) {
	element_ = docFactory.createElement("error");
	setType(condition.getType());
	setCondition(condition);
	setText(text, lang);
    }

    /**
     * Constructs a new StanzaError using existing error element.
     * 
     * @param element
     *            the error element
     */
    public StanzaErrors(Element element) {
	element_ = element;
    }

    /**
     * set the description of error element
     * 
     * @param text
     *            the description
     * @param lang
     *            the language
     */
    public void setText(String text, String lang) {
	Element txtElement = element_.element("text");
	if (text == null) {
	    if (txtElement != null) {
		element_.remove(txtElement);
	    }
	    return;
	}

	if (txtElement == null) {
	    txtElement = docFactory.createElement("text", ERROR_NAMESPACE);
	    if (lang != null) {
		txtElement.addAttribute(QName.get("lang", "xml",
			"http://www.w3.org/XML/1998/namespace"), lang);
	    }
	    element_.add(txtElement);
	}
	txtElement.setText(text);
    }

    /**
     * set error type
     * 
     * @param type
     *            error type
     * @see ErrorType
     */
    private void setType(ErrorType type) {
	if (type == null) {
	    element_.addAttribute("type", null);
	} else {
	    element_.addAttribute("type", type.getStringValue());
	}
    }

    /**
     * set the condition of error element
     * 
     * @param condition
     *            the condition error
     * @see Condition
     */
    @SuppressWarnings("unchecked")
    private void setCondition(Condition condition) {
	if (condition == null) {
	    throw new NullPointerException("Condition cannot be null");
	}

	element_.addAttribute("code", condition.getCode());
	Element condElement = null;
	for (Iterator i = element_.elementIterator(); i.hasNext();) {

	    Element element = (Element) i.next();
	    if (element.getNamespaceURI().equals(ERROR_NAMESPACE)
		    && !element.getName().equals("text")) {
		condElement = element;
	    }
	}
	if (condElement != null) {
	    element_.remove(condElement);
	}

	condElement = docFactory.createElement(condition.getStringValue(),
		ERROR_NAMESPACE);
	element_.add(condElement);
    }

    /**
     * return error element
     * 
     * @return error element
     */
    public Element getElement() {
	return element_;
    }

    /**
     * return the representation of error element as XML string
     * 
     * @return error element
     */
    public String toXML() {
	return element_.asXML();
    }

    /**
     * Override toSting() method
     */
    public String toString() {
	StringWriter out = new StringWriter();
	XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
	try {
	    writer.write(element_);
	} catch (Exception e) {
	    e.printStackTrace();
	}
	return out.toString();
    }

    /**
     * represent error types
     * 
     */
    public enum ErrorType {
	/**
	 * Do not retry (the error is unrecoverable)
	 */
	cancel("cancel"),
	/**
	 * Proceed (the condition was only a warning).
	 */

	continue_("continue"),
	/**
	 * Retry after changing the data sent.
	 */

	modify("modify"),
	/**
	 * Retry after providing credentials.
	 */

	auth("auth"),
	/**
	 * Retry after waiting (the error is temporary).
	 */

	wait("wait");

	private String value_;

	private ErrorType(String value) {
	    value_ = value;
	}

	/**
	 * return the representation of error type as string
	 * 
	 * @return
	 */
	public String getStringValue() {
	    return value_;
	}
    }

    /**
     * represent the error condition
     * 
     */
    public enum Condition {
	/**
	 * The feature requested is not implemented by the recipient or server
	 * and therefore cannot be processed; the associated error type SHOULD
	 * be "cancel".
	 */
	feature_not_implemented("feature-not-implemented", ErrorType.cancel,
		"501"),
	/**
	 * A remote server or service specified as part or all of the JID of the
	 * intended recipient does not exist; the associated error type SHOULD
	 * be "cancel".
	 */

	remote_server_not_found("remote-server-not-found", ErrorType.cancel,
		"404"),
	/**
	 * The requesting entity is not authorized to access the requested
	 * service because registration is required; the associated error type
	 * SHOULD be "auth".
	 */

	registration_required("registration-required", ErrorType.auth, "407"),
	/**
	 * The server could not process the stanza because of a misconfiguration
	 * or an otherwise-undefined internal server error; the associated error
	 * type SHOULD be "wait".
	 */

	internal_server_error("internal-server-error", ErrorType.wait, "500"),
	/**
	 * The intended recipient is temporarily unavailable; the associated
	 * error type SHOULD be "wait" (note: an application MUST NOT return
	 * this error if doing so would provide information about the intended
	 * recipient's network availability to an entity that is not authorized
	 * to know such information).
	 */

	recipient_unavailable("recipient-unavailable", ErrorType.wait, "404"),
	/**
	 * The addressed JID or item requested cannot be found; the associated
	 * error type SHOULD be "cancel".
	 */

	item_not_found("item-not-found", ErrorType.cancel, "404"),
	/**
	 * The sending entity has provided or communicated an XMPP address
	 * (e.g., a value of the 'to' attribute) or aspect thereof (e.g., a
	 * resource identifier) that does not adhere to the syntax defined in
	 * Addressing Scheme (Section 3); the associated error type SHOULD be
	 * "modify".
	 */

	jid_malformed("jid-malformed", ErrorType.modify, "400"),
	/**
	 * The recipient or server understands the request but is refusing to
	 * process it because it does not meet criteria defined by the recipient
	 * or server (e.g., a local policy regarding acceptable words in
	 * messages); the associated error type SHOULD be "modify".
	 */

	not_acceptable("not-acceptable", ErrorType.modify, "406"),
	/**
	 * The recipient or server does not allow any entity to perform the
	 * action; the associated error type SHOULD be "cancel".
	 */

	not_allowed("not-allowed", ErrorType.cancel, "405"),
	/**
	 * The sender must provide proper credentials before being allowed to
	 * perform the action, or has provided improper credentials; the
	 * associated error type SHOULD be "auth".
	 */

	not_authorized("not-authorized", ErrorType.auth, "401"),
	/**
	 * The requesting entity is not authorized to access the requested
	 * service because payment is required; the associated error type SHOULD
	 * be "auth".
	 */

	payment_required("payment-required", ErrorType.auth, "402"),
	/**
	 * A remote server or service specified as part or all of the JID of the
	 * intended recipient (or required to fulfill a request) could not be
	 * contacted within a reasonable amount of time; the associated error
	 * type SHOULD be "wait".
	 */

	remote_server_timeout("conflict", ErrorType.wait, "504"),
	/**
	 * The server or recipient lacks the system resources necessary to
	 * service the request; the associated error type SHOULD be "wait".
	 */

	resource_constraint("conflict", ErrorType.wait, "500"),
	/**
	 * The server or recipient does not currently provide the requested
	 * service; the associated error type SHOULD be "cancel".
	 */

	service_unavailable("conflict", ErrorType.cancel, "305"),
	/**
	 * The requesting entity is not authorized to access the requested
	 * service because a subscription is required; the associated error type
	 * SHOULD be "auth".
	 */

	subscription_required("conflict", ErrorType.auth, "705"),
	/**
	 * The error condition is not one of those defined by the other
	 * conditions in this list; any error type may be associated with this
	 * condition, and it SHOULD be used only in conjunction with an
	 * application-specific condition.
	 */

	undefined_condition("conflict", ErrorType.wait, "500"),
	/**
	 * The recipient or server understood the request but was not expecting
	 * it at this time (e.g., the request was out of order); the associated
	 * error type SHOULD be "wait".
	 */

	unexpected_request("conflict", ErrorType.wait, "400"),
	/**
	 * The sender has sent XML that is bad or that cannot be processed
	 * (e.g., an IQ stanza that includes an unrecognized value of the 'type'
	 * attribute). The associated error type SHOULD be "modify".
	 */

	bad_request("bad-request", ErrorType.wait, "400"),
	/**
	 * Access cannot be granted because an existing resource or session
	 * exists with the same name or address; the associated error type
	 * SHOULD be "cancel"
	 */

	conflict("conflict", ErrorType.cancel, "409"),
	/**
	 * The requesting entity does not possess the required permissions to
	 * perform the action; the associated error type SHOULD be "auth".
	 */

	forbidden("forbidden", ErrorType.auth, "403"),
	/**
	 * The recipient or server is redirecting requests for this information
	 * to another entity, usually temporarily (the error stanza SHOULD
	 * contain the alternate address, which MUST be a valid JID, in the XML
	 * character data of the &lt;redirect/&gt; element); the associated
	 * error type SHOULD be "modify".
	 */

	redirect("redirect", ErrorType.modify, "302"),
	/**
	 * he recipient or server can no longer be contacted at this address
	 * (the error stanza MAY contain a new address in the XML character data
	 * of the <gone/> element); the associated error type SHOULD be
	 * "modify".
	 */

	gone("conflict", ErrorType.modify, "302");

	private String value_;
	private ErrorType errorType_;
	private String code_;

	private Condition(String value, ErrorType type, String code) {
	    value_ = value;
	    errorType_ = type;
	    code_ = code;
	}

	public String getCode() {
	    return code_;
	}

	public ErrorType getType() {
	    return errorType_;
	}

	public String getStringValue() {
	    return value_;

	}
    }
}